<!--
 * 基于flv.js的视频播放器组件(支持拖动)
 * @Author: bobo
 * @Date: 2021-01-27 
-->
<template>
  <!-- 根布局容器 -->
  <div
    v-show="isShow"
    :style="{ width: width, height: height, 'background-color': bgColor, 'z-index': getZIndex }"
    :class="[supportDrag ? 'container-drag' : 'container-undrag']"
    ref="rootContainer"
    @mousemove="_handleShowControls($event)"
    @mouseleave="_handleShowControls($event)"
  >
    <!-- 视频容器 -->
    <div class="video-container">
      <video ref="videoEl" :poster="posterUrl">您的浏览器版本太低，不支持HTML5 Video!!!</video>
    </div>
    <div class="video-cover-container" ref="coverContainer">
      <!-- 用户自定义控制插槽 -->
      <slot name="custom-cover"></slot>
    </div>
  </div>
</template>

<script>
import flvjs from "flv.js";
export default {
  name: "BaseFlvPlayer",
  props: {
    //是否显示窗口
    isShow: {
      type: Boolean,
      default: true,
    },
    //是否开启拖动，默认关闭
    supportDrag: Boolean,
    //封面
    posterUrl: String,
    //播放窗体宽度
    width: {
      type: String,
      default: "400px",
    },
    //播放窗体高度
    height: {
      type: String,
      default: "228px",
    },
    //播放窗体背景色,默认黑色
    bgColor: {
      type: String,
      default: "#000000",
    },
    //窗口拖动时层级(supportDrag=true时生效)
    zIndex: {
      type: Number,
      default: 9999,
    },
  },
  data() {
    return {
      name: "BaseFlvPlayer",
      //flv播放器实例对象
      flvPlayer: null,
      //video实例对象
      videoEl: null,
      //根布局容器
      rootContainer: null,
      //视频覆盖层容器(媒体控制)
      coverContainer: null,
      videoOptions: {
        //视频格式类型
        mimeType: null,
        //视频宽度
        width: 0,
        //视频高度
        height: 0,
        //是否正在播放视频
        isPlaying: false,
        //是否全屏显示
        isFullScreen: false,
        //当前播放时长(秒)
        playTime: 0,
        //当前播放进度[0-100]
        playProgress: 0,
        //当前视频缓存进度[0-100]
        cacheProgress: 0,
        //视频总时长(秒)
        duration: 0,
        //是否使用默认的控制效果( true :isShowControls参数才有效)
        useDefaultControlsAnim: true,
        //是否显示媒体控制条
        isShowControls: false,
      },
      //内部变量定义
      inner: {
        //隐藏定时器的id
        hideControlsTimeoutId: null,
        //控制条高度(px)
        controlsHeight: 0,
        //播放控制条定时隐藏时间
        controlsHideTimeout: 3000,
        //禁止拖动播放窗口状态(用于解决媒体控制拖动与窗口拖动冲突)
        enableDragWindow: true,
        //用于解决视频点击播放与窗口拖动点击冲突的标识
        isMoveWindow: false,
      },
    };
  },
  computed: {
    canDrag() {
      return this.$props.supportDrag && this.inner.enableDragWindow && !this.videoOptions.isFullScreen;
    },
    getZIndex() {
      if (this.$props.supportDrag) {
        return this.$props.zIndex;
      } else {
        return null;
      }
    },
  },
  mounted() {
    //初始化
    this.rootContainer = this.$refs["rootContainer"];
    this.coverContainer = this.$refs["coverContainer"];
    this.videoEl = this.$refs["videoEl"];
    this._handleDragWindow(this.rootContainer);
    this._initControlListener();
    window.onresize = () => {
      if (this.canDrag) {
        //处理窗口变化[可视区域]
        const windowWidth = document.body.clientWidth;
        const windowHeight = document.body.clientHeight;
        const el = this.rootContainer;
        if (windowWidth < el.clientWidth || windowHeight < el.clientHeight) {
          //窗口太小，没必要处理了
          return;
        }
        //由于浏览器缩放是以坐标原点缩放(窗口左顶点)，故只需关注处理右底点的越界问题
        if (el.offsetLeft + el.offsetWidth > windowWidth) {
          el.style.left = windowWidth - el.offsetWidth + "px";
        }
        if (el.offsetTop + el.offsetHeight > windowHeight) {
          el.style.top = windowHeight - el.offsetHeight + "px";
        }
      }
    };
  },
  destroyed() {
    this.close();
  },
  methods: {
    _handleDragWindow(el) {
      if (!this.supportDrag) {
        return;
      }
      // 鼠标按下事件(使用事件捕获流程)
      el.onmousedown = (ed) => {
        if (!this.canDrag) {
          return;
        }
        this.inner.isMoveWindow = false;
        // 鼠标按下，计算当前元素距离可视区的距离
        const disX = ed.clientX - el.offsetLeft;
        const disY = ed.clientY - el.offsetTop;
        // 拖动元素的宽和高
        const dragDomWidth = el.offsetWidth;
        const dragDomHeight = el.offsetHeight;
        //可视区域的宽和高
        const screenWidth = document.body.clientWidth;
        const screenHeight = document.body.clientHeight;
        if (el.setCapture) {
          el.setCapture();
        }
        // 鼠标移动事件-----给文档流绑定移动事件
        document.onmousemove = (em) => {
          // 取消事件的默认动作。
          em.preventDefault();
          var L = em.clientX - disX;
          var T = em.clientY - disY;
          L = Math.min(Math.max(L, 0), screenWidth - dragDomWidth);
          T = Math.min(Math.max(T, 0), screenHeight - dragDomHeight);
          el.style.left = L + "px";
          el.style.top = T + "px";
          this.inner.isMoveWindow = true;
          el.style.cursor = "move";
        };
        // 鼠标离开事件
        document.onmouseup = () => {
          document.onmousemove = document.onmouseup = null;
          // 拖动后在解除事件锁定
          if (el.releaseCapture) {
            el.releaseCapture();
          }
          el.style.cursor = "default";
        };
      };
    },
    //初始化播放器的媒体控制
    _initControlListener() {
      //视频时长回调
      this.videoEl.addEventListener("loadedmetadata", () => {
        if (this.videoEl.duration != Infinity) {
          this.videoOptions.duration = this.videoEl.duration;
        }
      });
      //视频开始播放回调
      this.videoEl.addEventListener("play", () => {
        this.videoOptions.isPlaying = true;
      });
      //视频播放暂停回调
      this.videoEl.addEventListener("pause", () => {
        this.videoOptions.isPlaying = false;
      });
      //视频下载缓冲进度回调
      this.videoEl.addEventListener("progress", () => {
        if (this.videoEl.buffered.length == 0) return;
        this.videoOptions.cacheProgress = Math.floor((100 * this.videoEl.buffered.end(0)) / this.videoEl.duration);
      });
      // 监听视频播放时间
      this.videoEl.addEventListener("timeupdate", () => {
        this.videoOptions.playTime = this.videoEl.currentTime;
        //认为是直播流
        if (this.videoEl.duration == Infinity && this.flvPlayer.buffered.length != 0) {
          const bufEndTime = Math.floor(this.flvPlayer.buffered.end(0));
          //过滤掉bufEndTime异常
          if (bufEndTime - this.videoOptions.playTime > 10000) {
            console.log("直播时，buffered 异常:", bufEndTime);
            return;
          }
          //延时超过5s
          if (bufEndTime - this.videoOptions.playTime > 5) {
            this.seekTo(bufEndTime);
          }
          return;
        }
        const percentage = Math.floor((100 * this.videoEl.currentTime) / this.videoEl.duration);
        if (percentage != this.videoOptions.playProgress) {
          this.videoOptions.playProgress = percentage;
        }
      });
      // 监听结束播放事件
      this.videoEl.addEventListener("ended", () => {
        this.videoOptions.isPlaying = false;
        this._handleShowControls();
      });
      //全屏事件监听，兼容chrome、safari
      ["fullscreenchange", "webkitfullscreenchange", "mozfullscreenchange"].forEach((item) => {
        window.addEventListener(item, () => {
          this.videoOptions.isFullScreen = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen;
        });
      });
      // 点击视频区域可以进行播放或者暂停
      this.videoEl.addEventListener("click", () => {
        if (this.inner.isMoveWindow) {
          return;
        }
        if (this.videoEl.paused || this.videoEl.ended) {
          this.play();
        } else {
          this.pause();
        }
      });
    },
    _handleShowControls(event) {
      if (
        !this.videoOptions.useDefaultControlsAnim ||
        this.flvPlayer == null ||
        event == null ||
        event.type == null ||
        ("mousemove" != event.type && "mouseleave" != event.type)
      ) {
        return;
      }
      const rootBottom = this.rootContainer.getBoundingClientRect().bottom;
      const awaysShow = rootBottom - event.clientY <= this.inner.controlsHeight;
      if ("mouseleave" != event.type && awaysShow) {
        //鼠标在视频控制条范围内一直显示
        if (!this.videoOptions.isShowControls) {
          this.videoOptions.isShowControls = true;
        }
        if (this.inner.hideControlsTimeoutId != null) {
          clearTimeout(this.inner.hideControlsTimeoutId);
          this.inner.hideControlsTimeoutId = null;
        }
      } else if (!this.videoOptions.isShowControls || this.inner.hideControlsTimeoutId == null) {
        //鼠标不在视频控制条范围内移动,且进度条未显示，启动定时显示
        this.videoOptions.isShowControls = true;
        clearTimeout(this.inner.hideControlsTimeoutId);
        this.inner.hideControlsTimeoutId = setTimeout(() => {
          this.videoOptions.isShowControls = false;
          this.inner.hideControlsTimeoutId = null;
        }, this.inner.controlsHideTimeout);
      } else {
        //控制条定时显示时，移动事件进来不再处理
      }
    },
    /**
     * 初始化视频资源
     * @param  source FlvJs.MediaDataSource
     * @param config flvJs.Config
     */
    load(source, config) {
      if (this.flvPlayer != null) {
        this.flvPlayer.unload();
        this.flvPlayer.detachMediaElement();
        this.flvPlayer.destroy();
        this.flvPlayer = null;
      }
      if (flvjs.isSupported()) {
        this.flvPlayer = flvjs.createPlayer(
          source,
          config || {
            enableWorker: false,
            lazyLoadMaxDuration: 3 * 60,
            seekType: "range",
          },
        );
        this.flvPlayer.on(flvjs.Events.ERROR, (errType, errDetail, info) => {
          this.$emit("player-error", errType, errDetail, info);
        });
        this.flvPlayer.on("media_info", (info) => {
          this.videoOptions.width = info.width;
          this.videoOptions.height = info.height;
          this.videoOptions.mimeType = info.mimeType;
        });
        this.flvPlayer.attachMediaElement(this.videoEl);
        this.flvPlayer.load();
      }
      this.$emit("player-load");
    },
    play() {
      if (this.flvPlayer != null) {
        this.videoOptions.isPlaying = true;
        //这里认为总时长为空时是直播流,在点击播放时,解决直播时缓存堆积
        if (!this.duration && this.flvPlayer.buffered.length != 0) {
          //获取已经缓存的长度
          const bufferEndTime = Math.floor(this.flvPlayer.buffered.end(0));
          this.seekTo(bufferEndTime);
        }
        this.flvPlayer.play();
      }
      this.$emit("player-play");
    },
    pause() {
      if (this.flvPlayer != null) {
        this.videoOptions.isPlaying = false;
        this.flvPlayer.pause();
      }
      this.$emit("player-pause");
    },
    close() {
      if (this.flvPlayer != null) {
        this.flvPlayer.pause();
        this.flvPlayer.unload();
        this.flvPlayer.detachMediaElement();
        this.flvPlayer.destroy();
        this.flvPlayer = null;
      }
      this.videoOptions.isPlaying = false;
      this.videoOptions.isFullScreen = false;
      this.videoOptions.playTime = 0;
      this.videoOptions.playProgress = 0;
      this.videoOptions.cacheProgress = 0;
      this.$emit("player-closed");
    },
    /**
     * 设定播放时间为某个时间点(单位秒)
     */
    seekTo(time) {
      this.flvPlayer.currentTime = time;
    },
    /**
     * 重新加载播放(用于异常导致的暂停，重新播放)
     */
    reloadPlay() {
      this.flvPlayer.unload();
      this.flvPlayer.load();
      this.play();
    },
    fullScreen() {
      const rootView = this.rootContainer;
      /*使用能力测试添加不同浏览器下的前缀*/
      if (rootView.requestFullScreen) {
        rootView.requestFullScreen();
      } else if (rootView.webkitRequestFullScreen) {
        rootView.webkitRequestFullScreen();
      } else if (rootView.mozRequestFullScreen) {
        rootView.mozRequestFullScreen();
      } else if (rootView.msRequestFullScreen) {
        rootView.msRequestFullScreen();
      } else {
        //do nothing
      }
    },
    exitFullScreen() {
      if (document.cancelFullScreen) {
        document.cancelFullScreen();
      } else if (document.webkitCancelFullScreen) {
        document.webkitCancelFullScreen();
      } else if (document.mozCancelFullScreen) {
        document.mozCancelFullScreen();
      } else if (document.msCancelFullScreen) {
        document.msCancelFullScreen();
      } else {
        //do nothing
      }
    },
    /**
     * 设置视频播放填充类型，object-fit属性
     *fill | contain | cover | none | scale-down
     * 参考:https://developer.mozilla.org/zh-CN/docs/Web/CSS/object-fit
     *
     */
    setVideoScale(value) {
      if (this.flvPlayer == null) return;
      this.videoEl.style.objectFit = value;
    },
    /**
     * 设置控制条相关参数
     * {height:高度,timeout:定时隐藏时间}
     */
    setControlsOptions(options = {}) {
      this.inner.controlsHeight = options.height || 48;
      this.inner.controlsHideTimeout = options.timeout || 3000;
    },
    /**
     * 拖动播放窗口标识控制（可用于解决媒体控制拖动与窗口拖动冲突）
     */
    enableDragWindow(enable = true) {
      this.inner.enableDragWindow = enable;
    },
    getLoggingControl() {
      return flvjs.LoggingControl;
    },
  },
};
</script>

<style lang="scss" scoped>
// 定义播放窗口的基本样式
.baseContainer {
  width: 400px;
  height: 226px;
}
.container-drag {
  @extend .baseContainer;
  position: fixed;
}
.container-undrag {
  @extend .baseContainer;
  position: relative;
}
.video-container video {
  width: 100%;
  height: 100%;
  position: absolute;
  object-fit: contain;
  //解决组件覆盖导致点击失效问题
  pointer-events: auto;
}
.video-cover-container {
  position: absolute;
  width: 100%;
  height: 100%;
  //解决组件覆盖导致点击失效问题
  pointer-events: none;
}
</style>
